package spbun.fileclean.common;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.log4j.Logger;

public class FileUtil {
    /** 日志对象 */
    private static final Logger LOG = Logger.getLogger(FileUtil.class);

    /**
     * 缓存的webapp的目录
     */
    private static String cachedWebAppBasePath = null;

    /**
     * 清空一个目录，删除目录下的所有子文件及文件夹。但是不删除参数的目录。
     * 
     * @param forder
     * @throws IOException
     */
    public static void cleanForder(File forder) throws IOException {

        File[] files = forder.listFiles();

        IOException ioe = null;

        for (File file : files) {

            if (file.isDirectory()) {// 如果文件
                deleteForder(file);

            } else {

                // 我们使用尽量删除的方式，当删除一个文件失败，我们继续删除下一个文件
                try {
                    deleteFile(file);
                } catch (IOException e) {
                    LOG.error(e.getMessage(), e);
                    ioe = e;
                }
            }
        }

        if (ioe != null) {
            throw ioe;
        }
    }

    /**
     * 删除一个文件<br>
     * 因为java.io.File#delete方法删除的时候只返回一个boolean值，很多时候影响我们的程序开发结果，因此我们
     * 
     * @param file
     *            要被删除的文件
     * @throws IOException
     *             如果传入的不是一个文件(为文件夹)，则抛出异常。
     * @throws IOException
     *             如果传入的文件不存在，则抛出异常。
     * @throws IOException
     *             如果不能删除文件，同样抛出异常。
     */
    public static void deleteFile(File file) throws IOException {

        // 这里不使用if(!file.isFile)而使用file.isDirectory()
        // 因为：当文件不存在的时候，file#isFile，file#isDirectory都返回false
        // 当被删除的文件不存在的时候，会被!file.isFile拦截，导致返回的消息为“要删除的不是一个文件”
        if (file.isDirectory()) {
            throw new IOException("要删除的不是一个文件，文件：" + file);
        }

        boolean isFileExists = file.exists();

        if (!file.delete()) {// 如果删除失败
            if (!isFileExists) {
                throw new IOException("要删除的文件不存在，文件：" + file);
            }
            throw new IOException("无法删除文件，文件：" + file);
        }
    }

    /**
     * 删除一个文件夹以及文件夹下的所有文件及文件夹
     * 
     * @param forder
     * @return
     * @throws IOException
     */
    public static void deleteForder(File forder) throws IOException {

        if (!forder.exists()) {
            return;
        }

        cleanForder(forder);// 清空目录

        if (!forder.delete()) {
            throw new IOException("无法删除文件夹，文件：" + forder);
        }
    }

    /**
     * 获取文件大小
     * 
     * @param fileName
     * @return
     */
    public static String getFileSize(File fileName) {
        return getFileSize(fileName.length());
    }

    /**
     * 获取文件大小
     * 
     * @param fileName
     * @return
     */
    public static String getFileSize(long fileSize) {

        String size;
        BigDecimal bd = new BigDecimal(fileSize);
        BigDecimal kbd = bd.divide(new BigDecimal(1024), 0, BigDecimal.ROUND_UP);

        if (kbd.compareTo(new BigDecimal(1024)) > 0) {
            BigDecimal mbd = kbd.divide(new BigDecimal(1024), 2, BigDecimal.ROUND_UP);
            size = mbd.toString() + "M";
        } else {
            size = kbd.toString() + "K";
        }

        return size;
    }

    /**
     * 传入一个文件名，得到这个文件名称的后缀 包含 .
     * 
     * @param fileName
     *            - 文件名
     * @return
     */
    public static String getSuffixContainPoint(String fileName) {

        int index = fileName.lastIndexOf(".");

        if (index != -1) {
            String suffix = fileName.substring(index);// 后缀
            return suffix;
        } else {
            return null;
        }
    }

    /**
     * 传入一个文件名，得到这个文件名称的后缀 不包含 .
     * 
     * @param fileName
     *            - 文件名
     * @return
     */
    public static String getSuffixUnContainPoint(String fileName) {

        int index = fileName.lastIndexOf(".");

        if (index != -1) {
            String suffix = fileName.substring(index + 1);// 后缀

            return suffix.toLowerCase();
        } else {

            return null;
        }
    }

    /**
     * 获得项目的路径
     * 
     * @return 项目的路径
     */
    public static String getWebAppBasePath() {

        if (cachedWebAppBasePath != null) {
            return cachedWebAppBasePath;
        }

        Class<FileUtil> cls = FileUtil.class;

        ClassLoader loader = cls.getClassLoader();
        // 获得类的全名，包括包名
        String clsName = cls.getName() + ".class";
        // 获得传入参数所在的包
        Package pack = cls.getPackage();
        String path = "";
        // 如果不是匿名包，将包名转化为路径
        if (pack != null) {
            String packName = pack.getName();
            // 此处简单判定是否是Java基础类库，防止用户传入JDK内置的类库
            if (!packName.startsWith("spbun")) {
                throw new java.lang.IllegalArgumentException("类型不支持");
            }
            // 在类的名称中，去掉包名的部分，获得类的文件名
            clsName = clsName.substring(packName.length() + 1);
            // 判定包名是否是简单包名，如果是，则直接将包名转换为路径，
            if (packName.indexOf(".") < 0)
                path = packName + "/";
            else {// 否则按照包名的组成部分，将包名转换为路径
                int start = 0, end = 0;
                end = packName.indexOf(".");
                while (end != -1) {
                    path = path + packName.substring(start, end) + "/";
                    start = end + 1;
                    end = packName.indexOf(".", start);
                }
                path = path + packName.substring(start) + "/";
            }
        }

        // 调用ClassLoader的getResource方法，传入包含路径信息的类文件名
        java.net.URL url = loader.getResource(path + clsName);
        // 从URL对象中获取路径信息

        String realPath = url.getPath();
        // 去掉路径信息中的协议名"file:"
        int pos = realPath.indexOf("file:");
        if (pos > -1)
            realPath = realPath.substring(pos + 5);

        // 去掉路径信息最后包含类文件信息的部分，得到类所在的路径
        pos = realPath.indexOf(path + clsName);
        realPath = realPath.substring(0, pos - 1);

        // 如果类文件被打包到JAR等文件中时，去掉对应的JAR等打包文件名
        boolean isRar = realPath.endsWith("!");
        if (isRar) {
            realPath = realPath.substring(0, realPath.lastIndexOf("/"));
        }

        // 结果字符串可能因平台默认编码不同而不同。因此，改用 decode(String,String) 方法指定编码。
        try {
            realPath = java.net.URLDecoder.decode(realPath, "utf-8");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        File file = new File(realPath);

        cachedWebAppBasePath = file.getParentFile().getParentFile().getAbsolutePath();

        return cachedWebAppBasePath;
    }

    /**
     * 判断一个文件夹是否为另外一个文件夹的子文件夹
     * 
     * @param subFile
     *            要判断的文件夹
     * @param parentFile
     *            是否存于的父文件夹
     * @return 一个文件夹是否为另外一个文件夹的子文件夹。如果两个文件夹的路径相同，则返回false
     */
    public static boolean isSubFile(File subFile, File parentFile) {

        File subParent = subFile.getParentFile();

        if (subParent == null) {// 不能获取上级文件夹的话，证明此节点已经是根文件夹，根文件夹不会是其它文件夹的子文件夹
            return false;
        }

        if (parentFile.equals(subParent)) {
            return true;
        }

        // 如果两个目录相同，表示已经处理完成了，不再继续判断
        if (subFile.equals(subParent)) {
            return false;
        }

        return isSubFile(subParent, parentFile);
    }

    /**
     * 按照指定字符读取文件内容
     * 
     * @param file
     * @param encoding
     * @return
     * @throws IOException
     */
    public static byte[] readBytes(File file) throws IOException {

        if (file == null) {
            return null;
        }

        try (FileInputStream stream = new FileInputStream(file);
                ByteArrayOutputStream out = new ByteArrayOutputStream();) {

            byte[] b = new byte[1024 * 64]; // 缓存设置为64K
            int n;
            while ((n = stream.read(b)) != -1) {
                out.write(b, 0, n);
            }
            return out.toByteArray();
        }
    }

    /**
     * 按照指定字符读取文件内容
     * 
     * @param file
     * @param encoding
     * @return
     * @throws IOException
     */
    public static String readString(File file, String encoding) throws IOException {
        return new String(readBytes(file), encoding);
    }

    /**
     * 按照指定字符集将内容写入文件
     * 
     * @param content
     *            文本内容
     * @param file
     *            文件
     * @param encoding
     *            字符集
     * @param append
     *            是否为追加写入
     * @throws IOException
     */
    public static void writeString(String content, File file, String encoding, boolean append) throws IOException {

        FileOutputStream fos = null;

        try {

            fos = new FileOutputStream(file, append);

            fos.write(content.getBytes(encoding));

        } catch (IOException e) {
            LOG.error(e.getMessage());
            throw new IOException();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e1) {
                    LOG.error(e1.getMessage());
                }
            }
        }

    }

    /**
     * 按照指定字符集将内容写入文件
     * 
     * @param content
     *            文本内容
     * @param file
     *            文件
     * @param encoding
     *            字符集
     * @throws IOException
     */
    public static void writeString(String content, File file, String encoding) throws IOException {
        writeString(content, file, encoding, false);
    }

    /**
     * 子目录与根目录的相对路径
     * 
     * @param subFile
     *            子目录
     * @param rootFile
     *            根目录
     * @return 子目录与根目录的相对路径，如果子目录不再根目录下，返回null，如果子目录与根目录相同，返回空字符串。注意：返回值以文件分隔符开始。
     */
    public static String subPath(File subFile, File rootFile) {

        if (subFile.equals(rootFile)) {
            return "";
        }

        // 如果目录并不是特定目录的子目录，那么无法执行后续的处理，因此返回空。
        if (!isSubFile(subFile, rootFile)) {
            return null;
        }

        String parentPath = rootFile.getAbsolutePath();
        String subPath = subFile.getAbsolutePath();

        return subPath.substring(parentPath.length());
    }

    /**
     * 将文件夹的后面附加按照日期格式进行分割的子文件夹
     * 
     * @param forder
     * @param patterns
     * @return
     */
    public static File withDate(File forder, String... patterns) {

        if (patterns == null || patterns.length == 0) {
            throw new IllegalArgumentException("参数patterns不能为空！");
        }

        File result = forder;

        Date date = new Date();
        for (String pattern : patterns) {
            SimpleDateFormat sdf = new SimpleDateFormat(pattern);
            result = new File(result, sdf.format(date));
        }

        return result;
    }

    /**
     * 将文件夹的后面附加按照UUID分割后的格式进行分割的子文件夹
     * <p>
     * 因为uuid的长度为32，所以length*depth不能大于32。
     * </p>
     * <p>
     * 例子：forder="/usr/ifc/";uuid=06A64D81680044AAB5D26D4F97C2D715。
     * </p>
     * <p>
     * 如果length为2，depth为3的话，结果为/usr/ifc/06/A6/4D
     * </p>
     * <p>
     * 如果length为3，depth为2的话，结果为/usr/ifc/06A/64D
     * </p>
     * 
     * @param forder
     *            文件夹
     * @param uuid
     *            唯一ID
     * @return
     */
    public static File withUUID(File forder, String uuid) {
        return withUUID(forder, uuid, 2, 3);
    }

    /**
     * 将文件夹的后面附加按照UUID分割后的格式进行分割的子文件夹
     * <p>
     * 因为uuid的长度为32，所以length*depth不能大于32。
     * </p>
     * <p>
     * 例子：forder="/usr/ifc/";uuid=06A64D81680044AAB5D26D4F97C2D715。
     * </p>
     * <p>
     * 如果length为2，depth为3的话，结果为/usr/ifc/06/A6/4D
     * </p>
     * <p>
     * 如果length为3，depth为2的话，结果为/usr/ifc/06A/64D
     * </p>
     * 
     * @param forder
     *            文件夹
     * @param uuid
     *            唯一ID
     * @param length
     *            每个文件夹的长度
     * @param depth
     *            深度
     * @return
     */
    public static File withUUID(File forder, String uuid, int length, int depth) {

        if (length * depth > uuid.length()) {
            throw new IllegalArgumentException("参数length与depth的乘积不能大于uuid的长度！");
        }

        File result = forder;

        int off = 0;

        for (int i = 0; i < depth; i++) {
            result = new File(result, uuid.substring(off, off + length));
            off += length;
        }

        return result;
    }
}
